package com.questetra.OAuth.core
{
    import com.hurlant.crypto.Crypto;
    import com.hurlant.crypto.hash.HMAC;
    import com.hurlant.crypto.hash.IHash;
    import com.hurlant.util.Base64;
    import com.hurlant.util.Hex;
    
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IOErrorEvent;
    import flash.events.SecurityErrorEvent;
    import flash.net.URLLoader;
    import flash.net.URLRequest;
    import flash.net.URLRequestHeader;
    import flash.net.URLRequestMethod;
    import flash.net.URLVariables;
    import flash.utils.ByteArray;
    
    [Event(name="onRequestTokenReceived", type="com.questetra.OAuth.core.OAuthEvent")]
    [Event(name="onRequestTokenFailed", type="com.questetra.OAuth.core.OAuthEvent")]
    [Event(name="onAccessTokenReceived", type="com.questetra.OAuth.core.OAuthEvent")]
    [Event(name="onAccessTokenFailed", type="com.questetra.OAuth.core.OAuthEvent")]
    public class OAuthManager extends EventDispatcher
    {
        public function OAuthManager()
        {
            reset();
        }

        private var _url_request_token:String;
        private var _url_authorize:String;
        private var _url_access_token:String;
        private var _url_oauth_callback:String;
        
        private var _consumerKey:String = "";
        private var _consumerSecret:String = "";

        private var _authorizationToken:String = "";
        private var _authorizationTokenSecret:String = "";

        private var _accessToken:String = "";
        private var _accessTokenSecret:String = "";        
        private var _oauth_verifier:String = "";
        
        private var _signature_mode:Boolean = false;
        public function set url_request_token(value:String):void
        {
            this._url_request_token = value;
        }
        public function set url_authorize(value:String):void
        {
            this._url_authorize = value;
        }
        public function set url_access_token(value:String):void
        {
            this._url_access_token = value;
        }
        public function set url_oauth_callback(value:String):void
        {
            this._url_oauth_callback = value;
        }
        public function set consumerKey(value:String):void
        {
            this._consumerKey = value;
        }
        public function set consumerSecret(value:String):void
        {
            _consumerSecret = value;
        }

        // The authorization access token ... to be persisted for future requests
        public function set accessToken(value:String):void
        {
            _accessToken = value;
        }
        public function get accessToken():String
        {
            return _accessToken;
        }

        // The authorization access token secret ... to be persisted for future requests
        public function set accessTokenSecret(value:String):void
        {
            _accessTokenSecret = value;
        }
        public function get accessTokenSecret():String
        {
            return _accessTokenSecret;
        }
        
        // Returns whether this request has full authorisation
        public function isAuthorised():Boolean
        {
            return ( this._oauth_verifier && _accessToken.length );
        }

        // Resets all values to ensure multiple-requests do not get messed up
        public function reset():void{
            this._url_request_token = "";
            _consumerKey = "";
            _consumerSecret = "";
            _authorizationToken = "";
            _accessToken = "";
            _accessTokenSecret = "";
        }
        
        public function buildURLRequest(
            method:String,
            url:String,
            variables:URLVariables,
            isUseHeader:Boolean = false,
            oauth_body_hash:String = null
        ):URLRequest
        {
            trace("buildURLRequest");
            var request:URLRequest;
            var signed_variables:URLVariables;
            signed_variables = this.makeOAuthVariables(method, url, variables);
            
            if(method == URLRequestMethod.GET)
            {
                url += "?" + this.UrlVariableToArray(signed_variables).join("&");
                request = new URLRequest(url);
            }
            else
            {
                request = new URLRequest(url);
                request.data = signed_variables;
            }
            
            request.method = method;
            return request;
        }
        
        private function UrlVariableToArray(variables:URLVariables):Array
        {
            var arr:Array = new Array();
            for (var key:String in variables)
            {
                arr.push(key + "=" + encodeURIComponent(variables[key]));
            }
            arr.sort();
            return arr;
        }
        
        private function makeOAuthVariables(method:String, url:String, variables:URLVariables):URLVariables
        {
            if (!variables)
            {
                variables = new URLVariables();
            }
            
            // パラメタに必要な項目を追加する
            var timeStamp:String = Math.round( new Date().getTime() / 1000 ).toString();
            var nonce:String = Math.round( Math.random() * 10000 ).toString();
            variables.oauth_version = "1.0";
            variables.oauth_signature_method = "HMAC-SHA1";
            variables.oauth_nonce = nonce;
            variables.oauth_timestamp = timeStamp;
            variables.oauth_consumer_key = this._consumerKey;
            if (_accessToken.length)
            {
                variables.oauth_token =  this._accessToken;
            }
            else if (_authorizationToken.length)
            {
                variables.oauth_token = this._authorizationToken;
            }
            
            // シグネチャを作成する
            var sortArr:Array = this.UrlVariableToArray(variables);
            var sigBase:String = encodeURIComponent(method) + "&" +  encodeURIComponent(url)  + "&" + encodeURIComponent(sortArr.join("&"));
            trace("sigBase:" + sigBase);
            
            var sigkeybase:String = encodeURIComponent(this._consumerSecret) + "&";
            if(this._accessTokenSecret.length != 0){
                sigkeybase += encodeURIComponent(this._accessTokenSecret);
            }else if(this._authorizationTokenSecret.length !=0){
                sigkeybase += encodeURIComponent(this._authorizationTokenSecret);
            }
            
            var hmac:HMAC = Crypto.getHMAC("sha1");
            var sig_key:ByteArray = Hex.toArray( Hex.fromString(sigkeybase));
            
            var data:ByteArray = Hex.toArray(Hex.fromString(sigBase));
            var signature:String = Base64.encodeByteArray(hmac.compute(sig_key, data));
            
            // パラメタにシグネチャを追加
            variables.oauth_signature = signature;

            return variables;
        }
        

        // -----------------------------------------------------------------------------
        // STEP 1: Request an authorization token...
        public function requestToken():void
        {
            trace("Step.1\nrequestToken : AuthorizationToken(Secret)を請求します");
            var reqURI:String = this._url_request_token;
            var variables:URLVariables = new URLVariables();
            if (this._url_oauth_callback)
            {
                variables.oauth_callback = this._url_oauth_callback;
            }
            var request:URLRequest = this.buildURLRequest(URLRequestMethod.GET, this._url_request_token, variables);
            var loader:URLLoader = new URLLoader();
            loader.addEventListener(Event.COMPLETE, this.httpRequestTokenResult, false, 0, true);
            loader.addEventListener(IOErrorEvent.IO_ERROR, this.httpRequestTokenFault, false, 0, true);
            loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, this.httpRequestTokenFault, false, 0, true);
            
            trace("\t" + request.method);
            trace("\t" + request.url);
            loader.load(request);
        }

        private function httpRequestTokenResult(event:Event):void
        {
            trace("httpRequestTokenResult : AuthorizationToken(Secret)を取得しました");
            var loader:URLLoader = event.currentTarget as URLLoader;
            loader.removeEventListener(Event.COMPLETE, this.httpRequestTokenResult);
            loader.removeEventListener(IOErrorEvent.IO_ERROR, this.httpRequestTokenFault);
            loader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, this.httpRequestTokenFault);
            
            var result:String = loader.data;

            var qs:QueryString = new QueryString( result );
            if (qs.length)
            {
                _authorizationToken = qs.getValue("oauth_token");
                _authorizationTokenSecret = qs.getValue("oauth_token_secret");
                trace("\toauth_token:" + _authorizationToken);
                trace("\toauth_token_secret" + _authorizationTokenSecret);
            }
            
            if (_authorizationToken != null && _authorizationToken.length)
            {
                this.dispatchEvent( new OAuthEvent(OAuthEvent.ON_REQUEST_TOKEN_RECEIVED, result) );
            }
            else
            {
                var evt:OAuthEvent = new OAuthEvent(
                    OAuthEvent.ON_REQUEST_TOKEN_FAILED,
                    "Unable to receive request token because no valid response received. We got: " + result
                );
                this.dispatchEvent(evt);
                trace(evt.data);
                reset();
            }
        }
        
        private function httpRequestTokenFault(event:Event):void
        {
            trace("httpRequestTokenResult : AuthorizationToken(Secret)の取得に失敗しました");
            trace("\t下記の設定値は正しいですか？");
            trace("\t\turl_request_token,url_authorize,url_access_token,url_oauth_callback,");
            trace("\t\consumerKey,consumerSecret");

            var loader:URLLoader = event.currentTarget as URLLoader;
            loader.removeEventListener(Event.COMPLETE, this.httpRequestTokenResult);
            loader.removeEventListener(IOErrorEvent.IO_ERROR, this.httpRequestTokenFault);
            loader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, this.httpRequestTokenFault);
            
            var evt:OAuthEvent = new OAuthEvent(
                OAuthEvent.ON_REQUEST_TOKEN_FAILED,
                "Unable to request token from because: " + event
            );
            this.dispatchEvent(evt);
            trace(evt.data);
            reset();
        }

        // -----------------------------------------------------------------------------
        // STEP 2: Redirect to website for authorization...
        public function requestAuthorization():URLRequest
        {
            trace("Step.2\nrequestAuthorization : ユーザ認証を請求します");
            
            if (_authorizationToken.length == 0)
            {
                throw new Error("requestToken not called successfully yet");
            }
            var reqURI:String = this._url_authorize;
            reqURI += "?oauth_token=" + encodeURIComponent(this._authorizationToken);
            trace("\t認証画面のURLは...\n\t" + reqURI);
            trace("\t認証後のコールバックは...\n\t" + this._url_oauth_callback + "\n\t\t（認証後、verifierがパラメタで渡されます）");
            trace("\t[requestAccessToken]でverifierのセットを待ちます");
            return new URLRequest(reqURI);
        }


        // -----------------------------------------------------------------------------
        // STEP 3: Request an access token...
        public function requestAccessToken(verifier:String):void
        {
            trace("Step.3\nrequestAccessToken : AccessTokenを請求\n\tverifire = " + verifier);
            if (_authorizationToken.length == 0)
            {
                throw new Error("requestToken not called successfully yet");
            }

            this._oauth_verifier = verifier;
            var variables:URLVariables = new URLVariables();
            variables.oauth_verifier = verifier;
            
            var request:URLRequest = this.buildURLRequest(URLRequestMethod.GET, this._url_access_token, variables);
            var loader:URLLoader = new URLLoader();
            loader.addEventListener(Event.COMPLETE, this.httpAccessTokenResult, false, 0, true);
            loader.addEventListener(IOErrorEvent.IO_ERROR, this.httpAccessTokenFault, false, 0, true);
            loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, this.httpAccessTokenFault, false, 0, true);
            loader.load(request);

        }

        private function httpAccessTokenResult(event:Event):void
        {
            trace("httpAccessTokenResult : AccessTokenの取得に成功しました");
            var loader:URLLoader = event.currentTarget as URLLoader;
            loader.removeEventListener(Event.COMPLETE, this.httpAccessTokenResult);
            loader.removeEventListener(IOErrorEvent.IO_ERROR, this.httpAccessTokenFault);
            loader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, this.httpAccessTokenFault);
            
            var result:String = loader.data;
            trace("\thttpResult:\n\t\t" + result);

            var qs:QueryString = new QueryString(result);
            if (qs.length)
            {
                this._accessToken = qs.getValue("oauth_token");
                this._accessTokenSecret = qs.getValue( "oauth_token_secret" );
                trace("\t\t下記の認証済みトークンを保存して、リクエストに利用します");
                trace("\t\t\taccessToken:" + this._accessToken);
                trace("\t\t\taccessTokenSecret:" + this._accessTokenSecret);
            }

            if(_accessToken != null && _accessToken.length && _accessTokenSecret != null && _accessTokenSecret.length)
            {
                this.dispatchEvent(new OAuthEvent(OAuthEvent.ON_ACCESS_TOKEN_RECEIVED, result));
            }
            else
            {
                var evt:OAuthEvent = new OAuthEvent(
                    OAuthEvent.ON_ACCESS_TOKEN_FAILED,
                    "Unable to receive access token because no valid response received. We got: " + result
                );
                this.dispatchEvent(evt);
                trace(evt.data);
                reset();
            }
        }
        
        private function httpAccessTokenFault(event:Event):void
        {
            var loader:URLLoader = event.currentTarget as URLLoader;
            loader.removeEventListener(Event.COMPLETE, this.httpAccessTokenResult);
            loader.removeEventListener(IOErrorEvent.IO_ERROR, this.httpAccessTokenFault);
            loader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, this.httpAccessTokenFault);

            var evt:OAuthEvent = new OAuthEvent(
                OAuthEvent.ON_ACCESS_TOKEN_FAILED,
                "Unable to request access token from because: " + event
            );
            this.dispatchEvent(evt);
            trace(evt.data);
            reset();
        }
        
        public function request(method:String, url:String, variables:URLVariables):void
        {
            var urlRequest:URLRequest = this.buildURLRequest(method, url, variables);

            var loader:URLLoader = new URLLoader();
            loader.addEventListener(Event.COMPLETE, this.onRequestResult);
            //loader.addEventListener(IOErrorEvent.IO_ERROR, this.onRequestIOError);
            //loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, this.onRequestSecurityError);
            loader.load(urlRequest);
        }
        
        private function onRequestResult(e:Event):void
        {
            var loader:URLLoader = e.currentTarget as URLLoader;
            loader.removeEventListener(Event.COMPLETE, this.onRequestResult);
            trace(loader.data);
            this.dispatchEvent(new OAuthEvent(OAuthEvent.ON_REQUEST_COMPLETE, loader.data));
        }

    }
}
